<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
use App\Model\Admin\Admin;
use App\Model\Admin\AdminRole;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Hyperf\Collection\Arr;
use Hyperf\Collection\Collection;
use Hyperf\Context\ApplicationContext;
use Hyperf\Context\Context;
use Hyperf\Contract\SessionInterface;
use Hyperf\Coroutine\Coroutine;
use Hyperf\Guzzle\ClientFactory;
use Hyperf\Guzzle\HandlerStackFactory;
use Hyperf\Guzzle\PoolHandler;
use Hyperf\Guzzle\RetryMiddleware;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Snowflake\IdGeneratorInterface;
use Hyperf\Snowflake\Meta;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use function Hyperf\Support\make;

if (! function_exists('convert_size')) {
    /**
     * 将字节转化为 kb mb 等单位.
     */
    function convert_size(mixed $size): string
    {
        $unit = ['b', 'kb', 'mb', 'gb', 'tb', 'pb'];
        return @round($size / pow(1024, $i = floor(log($size, 1024))), 2) . ' ' . $unit[$i];
    }
}

/**
 * Snowflake 生成id.
 * @throws ContainerExceptionInterface
 * @throws NotFoundExceptionInterface
 */
function get_snowflake_id(): int
{
    $container = ApplicationContext::getContainer();
    $generator = $container->get(IdGeneratorInterface::class);
    return $generator->generate();
}

/**
 * Snowflake 当知道 ID 需要反推对应的 Meta 时，只需要调用 degenerate 即可.
 * @throws ContainerExceptionInterface
 * @throws NotFoundExceptionInterface
 */
function get_snowflake_meta(int $id): Meta
{
    $container = ApplicationContext::getContainer();
    $generator = $container->get(IdGeneratorInterface::class);
    return $generator->degenerate($id);
}

if (! function_exists('is_windows')) {
    /**
     * 是否为windows系统
     */
    function is_windows(): bool
    {
        return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? true : false;
    }
}

/**
 * 是否为mac系统
 */
function is_mac(): bool
{
    return preg_match('/macintosh|mac os x/i', getenv('HTTP_USER_AGENT')) ? true : false;
}

/**
 * 是否为linux系统
 */
function is_linux(): bool
{
    return strtoupper(PHP_OS) === 'LINUX' ? true : false;
}

if (! function_exists('get_last_month')) {
    /**
     * 上个月份的年月.
     *
     * @return false|string
     */
    function get_last_month(string $date)
    {
        $timestamp = strtotime($date);
        return date('Y-m', strtotime(date('Y', $timestamp) . '-' . (date('m', $timestamp) - 1)));
    }
}

if (! function_exists('get_next_month')) {
    /**
     * 下个月份的年月.
     *
     * @return false|string
     */
    function get_next_month(string $date)
    {
        $timestamp = strtotime($date);
        $arr = getdate($timestamp);
        if ($arr['mon'] == 12) {
            $year = $arr['year'] + 1;
            $month = $arr['mon'] - 11;
            $firstday = $year . '-0' . $month;
        } else {
            $firstday = date('Y-m', strtotime(date('Y', $timestamp) . '-' . (date('m', $timestamp) + 1)));
        }
        return $firstday;
    }
}

if (! function_exists('psQty_res')) {
    /**
     * 获取指定的进程.
     *
     * @return mixed
     */
    function psQty_res(string $search)
    {
        exec("ps aux | grep '" . $search . "' | grep -v grep | awk '{ print  }' | head -50", $result);
        return $result;
    }
}
if (! function_exists('psQty_limit')) {
    /**
     * 获取指定进程数，是否超过限制.
     *
     * @param string $search 进程名称
     * @param int $maxLimit 最大限制数
     *
     * @return bool
     */
    function psQty_limit(string $search, int $maxLimit = 2)
    {
        $result = psQty_res($search);
        if (count($result) - 1 >= $maxLimit) {
            return false;
        }
        return true;
    }
}

if (! function_exists('psQty_num')) {
    /**
     * 获取指定进程的数量.
     *
     * @return int
     */
    function psQty_num(string $search)
    {
        return count(psQty_res($search));
    }
}

if (! function_exists('run_exec')) {
    function run_exec(string $command_name)
    {
        $command = 'php ' . $command_name;

        $output = 0;

        $return_val = '';

        exec($command, $output, $return_val);

        return $output;
    }
}

if (! function_exists('get_difference_hours')) {
    /**
     * 计算两个时间戳之间相差的小时.
     *
     * @param int $start_time 开始时间戳
     * @param int $end_time 结束时间戳
     *
     * @return int
     */
    function get_difference_hours(int $start_time, int $end_time): float
    {
        if ($start_time < $end_time) {
            $starttime = $start_time;
            $endtime = $end_time;
        } else {
            $starttime = $end_time;
            $endtime = $start_time;
        }
        // 计算小时
        $timediff = $endtime - $starttime;
        return floatval($timediff / 3600);
    }
}

if (! function_exists('array_merge_multiple')) {
    /**
     * 多维数组合并.
     *
     * @param mixed $array1
     * @param mixed $array2
     * @return array
     */
    function array_merge_multiple($array1, $array2)
    {
        $merge = $array1 + $array2;
        $data = [];
        foreach ($merge as $key => $val) {
            if (isset($array1[$key]) && is_array($array1[$key]) && isset($array2[$key]) && is_array($array2[$key])) {
                $data[$key] = array_merge_multiple($array1[$key], $array2[$key]);
            } else {
                $data[$key] = isset($array2[$key]) ? $array2[$key] : $array1[$key];
            }
        }
        return $data;
    }
}

if (! function_exists('export_excel')) {
    /**
     * 数据导出到excel(csv文件).
     *
     * @param array $tileArray
     * @param array $dataArray
     * @param mixed $fileName
     */
    function export_excel($fileName, $tileArray = [], $dataArray = [])
    {
        ini_set('memory_limit', '1024M');
        ini_set('max_execution_time', 0);
        ob_end_clean();
        ob_start();
        header('Content-Type: text/csv');
        header('Content-Disposition:filename=' . $fileName);
        $fp = fopen('php://output', 'w');
        fwrite($fp, chr(0xEF) . chr(0xBB) . chr(0xBF)); // 转码 防止乱码(比如微信昵称)
        fputcsv($fp, $tileArray);
        $index = 0;
        foreach ($dataArray as $item) {
            if ($index == 1000) {
                $index = 0;
                ob_flush();
                flush();
            }
            ++$index;
            fputcsv($fp, $item);
        }
        ob_flush();
        flush();
        ob_end_clean();
    }
}

if (! function_exists('is_json')) {
    /**
     * 判断字符串是否为 Json 格式.
     *
     * @param string $data Json 字符串
     * @param bool $assoc 是否返回关联数组。默认返回对象
     *
     * @return array|bool|object 成功返回转换后的对象或数组，失败返回 false
     */
    function is_json($data = '', $assoc = false)
    {
        if (PHP_VERSION > 5.3) {
            json_decode($data);
            return json_last_error() == JSON_ERROR_NONE;
        }
        $data = json_decode($data, $assoc);
        if (($data && is_object($data)) || (is_array($data) && ! empty($data))) {
            return $data;
        }
        return false;
    }
}

if (function_exists('get_request_method')) {
    function get_request_method(): string
    {
        return strtoupper($_SERVER['REQUEST_METHOD'] ?? '');
    }
}

if (! function_exists('my_json_encode')) {
    /**
     * 统一的json_encode.
     *
     * @param array $data
     *
     * @return false|string
     */
    function my_json_encode($data, string $options = '')
    {
        // $data = is_object($data) ? (array)$data : $data;
        return json_encode($data, empty($options) ? (JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $options);
    }
}

if (! function_exists('my_json_decode')) {
    /**
     * 统一的 json_decode.
     *
     * @return mixed
     */
    function my_json_decode(string $string, bool $assoc = true)
    {
        return json_decode($string, $assoc);
    }
}

/**
 * 输出xml字符.
 * @param mixed $arr
 */
function ToXml($arr = [])
{
    if (! is_array($arr) || count($arr) <= 0) {
        return new \App\Exception\BusinessException(1,"数组数据异常！");
    }

    $xml = '<xml>';
    foreach ($arr as $key => $val) {
        if (is_numeric($val)) {
            $xml .= '<' . $key . '>' . $val . '</' . $key . '>';
        } else {
            $xml .= '<' . $key . '><![CDATA[' . $val . ']]></' . $key . '>';
        }
    }
    $xml .= '</xml>';
    return $xml;
}

/**
 * 将xml转为array.
 * @param mixed $xml
 * @return array|mixed
 */
function xml_to_array(mixed $xml): mixed
{
    if (! $xml) {
        return new \App\Exception\BusinessException(1,"xml数据异常！");
    }

    // 解决部分json数据误入的问题
    $arr = json_decode($xml, true);
    if (is_array($arr) && ! empty($arr)) {
        return $arr;
    }
    // 将XML转为array
    return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
}

if (! function_exists('make_signature')) {
    /**
     * 生成签名.
     *
     * @return string
     */
    function make_signature(array $args, string $key)
    {
        unset($args['sign']);
        // 排序
        ksort($args);
        // 生成sign
        $str = urldecode(http_build_query($args)) . '&key=' . $key;
        return strtoupper(sha1($str));
    }
}

// 生成签名
function make_sign($paraMap = [], $partner_key = '')
{
    unset($paraMap['sign']);
    $buff = '';
    ksort($paraMap);
    $paraMap['key'] = $partner_key;
    foreach ($paraMap as $k => $v) {
        if ($v != null && $v != 'null' && $v != '' && $k != 'sign') {
            $buff .= $k . '=' . $v . '&';
        }
    }
    $reqPar = '';
    if (strlen($buff) > 0) {
        $reqPar = substr($buff, 0, strlen($buff) - 1);
    }
    return strtoupper(sha1($reqPar));
}

/**
 * 前端获取后端的数据结果集.
 *
 * @param mixed $data
 * @return string
 */
function html_get_res_from_admin(mixed $data): string
{
    return addslashes(my_json_encode($data));
}



if (! function_exists('string_underscore_lowercase')) {
    /**
     * 字符串如果存在大小，那么自动转换成 _小写
     * @param mixed $string
     * @param string $format
     * @return array|string|string[]|null
     */
    function string_underscore_lowercase(mixed $string, string $format = '_'): array|string|null
    {
        return preg_replace('/((?<=[a-z])(?=[A-Z]))/', $format, $string);
    }
}

if (! function_exists('convert_underline')) {
    // 下划线的字符串转骆驼峰
    function convert_underline($str, $ucfirst = true): array|string
    {
        $str = ucwords(str_replace('_', ' ', $str));
        $str = str_replace(' ', '', lcfirst($str));
        return $ucfirst ? ucfirst($str) : $str;
    }
}

if (! function_exists('get_string_pluralize')) {
    /**
     * 获得指定字符串的复数.
     *
     * @return null|mixed|string|string[]
     */
    function get_string_pluralize(string $string)
    {
        $plural = [
            [
                '/(quiz)$/i',
                '$1zes',
            ],
            [
                '/^(ox)$/i',
                '$1en',
            ],
            [
                '/([m|l])ouse$/i',
                '$1ice',
            ],
            [
                '/(matr|vert|ind)ix|ex$/i',
                '$1ices',
            ],
            [
                '/(x|ch|ss|sh)$/i',
                '$1es',
            ],
            [
                '/([^aeiouy]|qu)y$/i',
                '$1ies',
            ],
            [
                '/([^aeiouy]|qu)ies$/i',
                '$1y',
            ],
            [
                '/(hive)$/i',
                '$1s',
            ],
            [
                '/(?:([^f])fe|([lr])f)$/i',
                '$1$2ves',
            ],
            [
                '/sis$/i',
                'ses',
            ],
            [
                '/([ti])um$/i',
                '$1a',
            ],
            [
                '/(buffal|tomat)o$/i',
                '$1oes',
            ],
            [
                '/(bu)s$/i',
                '$1ses',
            ],
            [
                '/(alias|status)$/i',
                '$1es',
            ],
            [
                '/(octop|vir)us$/i',
                '$1i',
            ],
            [
                '/(ax|test)is$/i',
                '$1es',
            ],
            [
                '/s$/i',
                's',
            ],
            [
                '/$/',
                's',
            ],
        ];

        $singular = [
            [
                '/s$/',
                '',
            ],
            [
                '/(n)ews$/',
                '$1ews',
            ],
            [
                '/([ti])a$/',
                '$1um',
            ],
            [
                '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/',
                '$1$2sis',
            ],
            [
                '/(^analy)ses$/',
                '$1sis',
            ],
            [
                '/([^f])ves$/',
                '$1fe',
            ],
            [
                '/(hive)s$/',
                '$1',
            ],
            [
                '/(tive)s$/',
                '$1',
            ],
            [
                '/([lr])ves$/',
                '$1f',
            ],
            [
                '/([^aeiouy]|qu)ies$/',
                '$1y',
            ],
            [
                '/(s)eries$/',
                '$1eries',
            ],
            [
                '/(m)ovies$/',
                '$1ovie',
            ],
            [
                '/(x|ch|ss|sh)es$/',
                '$1',
            ],
            [
                '/([m|l])ice$/',
                '$1ouse',
            ],
            [
                '/(bus)es$/',
                '$1',
            ],
            [
                '/(o)es$/',
                '$1',
            ],
            [
                '/(shoe)s$/',
                '$1',
            ],
            [
                '/(cris|ax|test)es$/',
                '$1is',
            ],
            [
                '/([octop|vir])i$/',
                '$1us',
            ],
            [
                '/(alias|status)es$/',
                '$1',
            ],
            [
                '/^(ox)en/',
                '$1',
            ],
            [
                '/(vert|ind)ices$/',
                '$1ex',
            ],
            [
                '/(matr)ices$/',
                '$1ix',
            ],
            [
                '/(quiz)zes$/',
                '$1',
            ],
        ];

        $irregular = [
            [
                'move',
                'moves',
            ],
            [
                'sex',
                'sexes',
            ],
            [
                'child',
                'children',
            ],
            [
                'man',
                'men',
            ],
            [
                'person',
                'people',
            ],
        ];

        $uncountable = [
            'sheep',
            'fish',
            'series',
            'species',
            'money',
            'rice',
            'information',
            'equipment',
        ];

        if (in_array(strtolower($string), $uncountable)) {
            return $string;
        }

        foreach ($irregular as $noun) {
            if (strtolower($string) == $noun[0]) {
                return $noun[1];
            }
        }
        foreach ($plural as $pattern) {
            $str = preg_replace($pattern[0], $pattern[1], $string);
            if ($str !== null && $str != $string) {
                return $str;
            }
        }
    }
}

if (! function_exists('get_month_range')) {
    /**
     * 指定日期范围之内的所有月份.
     *
     * @param string $start_date 开始日期
     * @param string $end_date 结束日期
     * @param string $format 返回格式
     *
     * @return array
     */
    function get_month_range(string $start_date, string $end_date, string $format = 'Y-m')
    {
        $end = date($format, strtotime($end_date)); // 转换为月
        $range = [];
        $i = 0;
        do {
            $month = date($format, strtotime($start_date . ' + ' . $i . ' month'));
            $range[] = $month;
            ++$i;
        } while ($month < $end);
        return $range;
    }
}

if (! function_exists('get_year_range')) {
    function get_year_range(string $start_date, string $end_date, string $format = 'Y')
    {
        $end = date($format, strtotime($end_date)); // 转换为月
        $range = [];
        $i = 0;
        do {
            $month = date($format, strtotime($start_date . ' + ' . $i . ' year'));
            $range[] = $month;
            ++$i;
        } while ($month < $end);
        return $range;
    }
}

if (! function_exists('load_vendor')) {
    /**
     * 加载composer的包 - vendor文件目录下.
     */
    function load_vendor(): void
    {
        /*
        |--------------------------------------------------------------------------
        | Register The Auto Loader
        |--------------------------------------------------------------------------
        |
        | Composer provides a convenient, automatically generated class loader for
        | our application. We just need to utilize it! We'll simply require it
        | into the script here so that we don't have to worry about manual
        | loading any of our classes later on. It feels great to relax.
        |
        */
        require BASE_PATH . '/vendor/autoload.php';
    }
}

if (! function_exists('data_get')) {
    /**
     * Get an item from an array or object using "dot" notation.
     *
     * @param mixed $target
     * @param array|int|string $key
     * @param mixed $default
     *
     * @return mixed
     */
    function data_get($target, $key, $default = null)
    {
        if (is_null($key)) {
            return $target;
        }

        $key = is_array($key) ? $key : explode('.', $key);

        while (! is_null($segment = array_shift($key))) {
            if ($segment === '*') {
                if ($target instanceof Collection) {
                    $target = $target->all();
                } elseif (! is_array($target)) {
                    return $default;
                }

                $result = [];

                foreach ($target as $item) {
                    $result[] = data_get($item, $key);
                }

                return in_array('*', $key) ? Arr::collapse($result) : $result;
            }

            if (Arr::accessible($target) && Arr::exists($target, $segment)) {
                $target = $target[$segment];
            } elseif (is_object($target) && isset($target->{$segment})) {
                $target = $target->{$segment};
            } else {
                return $default;
            }
        }

        return $target;
    }
}

if (! function_exists('data_set')) {
    /**
     * Set an item on an array or object using dot notation.
     *
     * @param mixed $target
     * @param array|string $key
     * @param mixed $value
     * @param bool $overwrite
     *
     * @return mixed
     */
    function data_set(&$target, $key, $value, $overwrite = true)
    {
        $segments = is_array($key) ? $key : explode('.', $key);

        if (($segment = array_shift($segments)) === '*') {
            if (! Arr::accessible($target)) {
                $target = [];
            }

            if ($segments) {
                foreach ($target as &$inner) {
                    data_set($inner, $segments, $value, $overwrite);
                }
            } elseif ($overwrite) {
                foreach ($target as &$inner) {
                    $inner = $value;
                }
            }
        } elseif (Arr::accessible($target)) {
            if ($segments) {
                if (! Arr::exists($target, $segment)) {
                    $target[$segment] = [];
                }

                data_set($target[$segment], $segments, $value, $overwrite);
            } elseif ($overwrite || ! Arr::exists($target, $segment)) {
                $target[$segment] = $value;
            }
        } elseif (is_object($target)) {
            if ($segments) {
                if (! isset($target->{$segment})) {
                    $target->{$segment} = [];
                }

                data_set($target->{$segment}, $segments, $value, $overwrite);
            } elseif ($overwrite || ! isset($target->{$segment})) {
                $target->{$segment} = $value;
            }
        } else {
            $target = [];

            if ($segments) {
                data_set($target[$segment], $segments, $value, $overwrite);
            } elseif ($overwrite) {
                $target[$segment] = $value;
            }
        }

        return $target;
    }
}

if (! function_exists('http_response_code')) {
    function http_response_code($code = null)
    {
        if ($code !== null) {
            switch ($code) {
                case 100:
                    $text = 'Continue';
                    break;
                case 101:
                    $text = 'Switching Protocols';
                    break;
                case 200:
                    $text = 'OK';
                    break;
                case 201:
                    $text = 'Created';
                    break;
                case 202:
                    $text = 'Accepted';
                    break;
                case 203:
                    $text = 'Non-Authoritative Information';
                    break;
                case 204:
                    $text = 'No Content';
                    break;
                case 205:
                    $text = 'Reset Content';
                    break;
                case 206:
                    $text = 'Partial Content';
                    break;
                case 300:
                    $text = 'Multiple Choices';
                    break;
                case 301:
                    $text = 'Moved Permanently';
                    break;
                case 302:
                    $text = 'Moved Temporarily';
                    break;
                case 303:
                    $text = 'See Other';
                    break;
                case 304:
                    $text = 'Not Modified';
                    break;
                case 305:
                    $text = 'Use Proxy';
                    break;
                case 400:
                    $text = 'Bad Request';
                    break;
                case 401:
                    $text = 'Unauthorized';
                    break;
                case 402:
                    $text = 'Payment Required';
                    break;
                case 403:
                    $text = 'Forbidden';
                    break;
                case 404:
                    $text = 'Not Found';
                    break;
                case 405:
                    $text = 'Method Not Allowed';
                    break;
                case 406:
                    $text = 'Not Acceptable';
                    break;
                case 407:
                    $text = 'Proxy Authentication Required';
                    break;
                case 408:
                    $text = 'Request Time-out';
                    break;
                case 409:
                    $text = 'Conflict';
                    break;
                case 410:
                    $text = 'Gone';
                    break;
                case 411:
                    $text = 'Length Required';
                    break;
                case 412:
                    $text = 'Precondition Failed';
                    break;
                case 413:
                    $text = 'Request Entity Too Large';
                    break;
                case 414:
                    $text = 'Request-URI Too Large';
                    break;
                case 415:
                    $text = 'Unsupported Media Type';
                    break;
                case 500:
                    $text = 'Internal Server Error';
                    break;
                case 501:
                    $text = 'Not Implemented';
                    break;
                case 502:
                    $text = 'Bad Gateway';
                    break;
                case 503:
                    $text = 'Service Unavailable';
                    break;
                case 504:
                    $text = 'Gateway Time-out';
                    break;
                case 505:
                    $text = 'HTTP Version not supported';
                    break;
                default:
                    exit('Unknown http status code "' . htmlentities($code) . '"');
                    break;
            }
            $protocol = ($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0');
            header($protocol . ' ' . $code . ' ' . $text);
            $GLOBALS['http_response_code'] = $code;
        } else {
            $code = ($GLOBALS['http_response_code'] ?? 200);
        }
        return $code;
    }
}

if (! function_exists('filtering_html_tags')) {
    /**
     * 过滤HTML的标签，只保留文本.
     * @param string $string
     * @return string
     */
    function filtering_html_tags(string $string): string
    {
        return strip_tags(htmlspecialchars_decode($string), '');
    }
}

if (! function_exists('hash_make')) {
    /**
     * hash加密.
     */
    function hash_make(string $password = '123456'): string
    {
        return hash_encryption($password);
    }
}

if (! function_exists('hash_encryption')) {
    /**
     * hash加密
     * @param string $pass
     * @return string
     */
    function hash_encryption(string $pass = '123456'): string
    {
        return password_hash($pass, PASSWORD_DEFAULT);
    }
}

if (! function_exists('hash_verify')) {
    /**
     * hash解密
     * @param string $pass
     * @param string $hash_pass
     * @return bool
     */
    function hash_verify(string $pass, string $hash_pass): bool
    {
        return password_verify($pass, $hash_pass);
    }
}

/**
 * 数组按照指定字段进行分组.
 */
function array_field_group(array $array = [], string $field = ''): array
{
    $new_ary = [];
    foreach ($array as $k => $val) {
        $new_ary[$val[$field]][] = $val;
    }
    return $new_ary;
}

// 计算中奖概率
function get_random_probability($proArr)
{
    $rs = ''; // z中奖结果
    $proSum = array_sum($proArr); // 概率数组的总概率精度
    // 概率数组循环
    foreach ($proArr as $key => $proCur) {
        $randNum = mt_rand(0, $proSum);
        if ($randNum <= $proCur) {
            $rs = $key;
            break;
        }  $proSum -= $proCur;
    }
    unset($proArr);
    return $rs;
}

/**
 * 对比数组的不同：
 *  第二个数组对于第一个数组的不同数据返回
 * 并不是array_diff数组的效果，找两个数组之间的差集.
 *
 * @param mixed $array1
 * @param mixed $array2
 * @return array
 */
function get_array_diff($array1, $array2)
{
    if (empty($array2)) {
        return $array1;
    }
    $diff = [];
    foreach ($array1 as $key => $val) {
        if (! isset($array2[$val])) {
            $diff[$val] = $val;
        }
    }
    return $diff;
}

function list_to_tree($list, $primary_key = 'menu_id', $pid = 'parent_id', $child = '_child', $root = 0): array
{
    $tree = [];
    if (is_array($list)) {
        $refer = [];
        foreach ($list as $key => $data) {
            $refer[$data[$primary_key]] = &$list[$key];
        }
        foreach ($list as $key => $data) {
            $parentId = $data[$pid];
            if ($root == $parentId) {
                $tree[] = &$list[$key];
            } else {
                if (isset($refer[$parentId])) {
                    $parent = &$refer[$parentId];
                    $parent[$child][] = &$list[$key];
                }
            }
        }
    }
    return $tree;
}

/**
 * [login_token].
 *
 * @param int $length [description]
 *
 * @chineseAnnotation:登录token值
 * @englishAnnotation:
 */
function login_token($length = 60): string
{
    // , '#', '%', '&', '+', '^', '`' --- 会影响参数获取，参数获取不全
    $chars = [
        'a',
        'b',
        'c',
        'd',
        'e',
        'f',
        'g',
        'h',
        'i',
        'j',
        'k',
        'l',
        'm',
        'n',
        'o',
        'p',
        'q',
        'r',
        's',
        't',
        'u',
        'v',
        'w',
        'x',
        'y',
        'z',
        'A',
        'B',
        'C',
        'D',
        'E',
        'F',
        'G',
        'H',
        'I',
        'J',
        'K',
        'L',
        'M',
        'N',
        'O',
        'P',
        'Q',
        'R',
        'S',
        'T',
        'U',
        'V',
        'W',
        'X',
        'Y',
        'Z',
        '0',
        '1',
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
        '!',
        '@',
        '$',
        '*',
        '(',
        ')',
        '-',
        '_',
        '[',
        ']',
        '{',
        '}',
        '<',
        '>',
        '~',
        '=',
        ',',
        '.',
        ';',
        ':',
        '/',
        '?',
        '|',
    ];
    $array = rand_array_fill($chars, $length);
    $rand = '';
    for ($i = 0; $i < $length; ++$i) {
        $rand .= $chars[$array[$i]];
    }

    unset($length, $array, $chars);

    return $rand;
}

function rand_array_fill(&$ary, $length)
{
    $count = count($ary);
    if ($count < $length) {
        $new_ary = [];
        for ($i = 0; $i < ceil($length / $count); ++$i) {
            for ($j = 0; $j < count($ary); ++$j) {
                array_push($new_ary, $ary[$j]);
            }
        }
        $ary = $new_ary;
        return array_rand($new_ary, $length);
    }  return array_rand($ary, $length);
}

if (! function_exists('get_client_info')) {
    /**
     * 获取IP与浏览器信息、语言
     */
    function get_client_info(): array
    {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $XFF = $_SERVER['HTTP_X_FORWARDED_FOR'];
            $client_pos = strpos($XFF, ', ');
            $client_ip = $client_pos !== false ? substr($XFF, 0, $client_pos) : $XFF;
            unset($XFF, $client_pos);
        } else {
            $client_ip = $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? $_SERVER['LOCAL_ADDR'] ?? '0.0.0.0';
        }
        $client_lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 5) : '';
        $client_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        return [
            'ip' => &$client_ip,
            'lang' => &$client_lang,
            'agent' => &$client_agent,
        ];
    }
}

if (! function_exists('get_ip')) {
    function get_ip(): string
    {
        $data = get_client_info();
        return $data['ip'] ?? '';
    }
}

if (! function_exists('get_this_url')) {
    function get_this_url()
    {
        return 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . '?' . $_SERVER['QUERY_STRING'];
    }
}

if (! function_exists('get_request_url')) {
    function get_request_url()
    {
        return $_SERVER['REQUEST_URI'];
    }
}

/**
 * request_post 模拟post进行url请求
 * @return array|false|object
 */
function request_post(string $url = '', array $post_data = [], bool $ispost = true, string $type = 'json')
{
    @header('Content-type: text/html; charset=utf-8');
    if (empty($url)) {
        return false;
    }
    $o = '';
    if (! empty($post_data)) {
        foreach ($post_data as $k => $v) {
            $o .= "{$k}=" . urlencode($v) . '&';
        }
        $post_data = substr($o, 0, -1);
        $key = md5(base64_encode($post_data));
    } else {
        $key = 'key';
    }
    if ($ispost) {
        $curlPost = $post_data;
    } else {
        $url = $url . '?' . implode(',', $post_data);
        $curlPost = 'key=' . $key;
    }
    $ch = curl_init(); // 初始化curl
    curl_setopt($ch, CURLOPT_URL, $url); // 抓取指定网页
    curl_setopt($ch, CURLOPT_HEADER, 0); // 设置header
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 要求结果为字符串且输出到屏幕上
    if ($ispost) {
        curl_setopt($ch, CURLOPT_POST, 1); // post提交方式
        curl_setopt($ch, CURLOPT_POSTFIELDS, $curlPost);
    }
    $data = curl_exec($ch); // 运行curl
    curl_close($ch);
    $object = json_decode($data);
    return object_to_array($object);
}

/**
 * 对象转为数组
 * @param $array
 * @return array|mixed
 */
function object_to_array($array)
{
    if (is_object($array)) {
        $array = (array) $array;
    }
    if (is_array($array)) {
        foreach ($array as $key => $value) {
            $array[$key] = object_to_array($value);
        }
    }
    return $array;
}

/**
 * 配置多维数组的解析
 * @param $data
 * @return array|false|string[]
 */
function config_array_analysis($data)
{
    $value_extra = preg_split('/[,;\r\n]+/', trim($data, ",;\r\n"));
    if (strpos($data, ':')) {
        $array = [];
        foreach ($value_extra as $val) {
            [
                $k,
                $v,
            ] = explode(':', $val);
            $array[$k] = $v;
        }
    } else {
        $array = $value_extra;
    }
    return $array ?? [];
}

/**
 * 检测是否为base64位编码
 * @param $str
 * @return bool
 */
function is_base64($str)
{
    // 这里多了个纯字母和纯数字的正则判断
    if (@preg_match('/^[0-9]*$/', $str) || @preg_match('/^[a-zA-Z]*$/', $str)) {
        return false;
    }
    if (is_utf8(base64_decode($str)) && base64_decode($str) != '') {
        return true;
    }
    return false;
}

/**
 * 判断否为UTF-8编码
 * @param $str
 * @return bool
 */
function is_utf8($str)
{
    $len = strlen($str);
    for ($i = 0; $i < $len; ++$i) {
        $c = ord($str[$i]);
        if ($c > 128) {
            if ($c > 247) {
                return false;
            }
            if ($c > 239) {
                $bytes = 4;
            } elseif ($c > 223) {
                $bytes = 3;
            } elseif ($c > 191) {
                $bytes = 2;
            } else {
                return false;
            }
            if (($i + $bytes) > $len) {
                return false;
            }
            while ($bytes > 1) {
                ++$i;
                $b = ord($str[$i]);
                if ($b < 128 || $b > 191) {
                    return false;
                }
                --$bytes;
            }
        }
    }
    return true;
}

function getMillisecond()
{
    [$t1, $t2] = explode(' ', microtime());
    return (float) sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
}

/*
 * 检测URL地址格式
 */
if (! function_exists('check_url')) {
    function check_url(string $url): bool
    {
        $str = "/^http(s?):\\/\\/(?:[A-za-z0-9-]+\\.)+[A-za-z]{2,4}(?:[\\/\\?#][\\/=\\?%\\-&~`@[\\]\\':+!\\.#\\w]*)?$/";
        if (! preg_match($str, $url)) {
            return false;
        }  return true;
    }
}

if (! function_exists('filter_url')) {
    function filter_url(string $url): bool
    {
        return filter_var($url, FILTER_VALIDATE_URL) !== false ? false : true;
    }
}

/**
 * 数组升序转成字符串
 * @param $data
 * @return string
 */
function array_ksort_to_string($data): string
{
    if (is_string($data)) {
        return $data;
    }
    ksort($data);
    $tmps = [];
    foreach ($data as $k => $v) {
        $tmps[] = $k . $v;
    }
    return implode('', $tmps);
}

/**
 * 是否为手机端访问
 * @return bool
 */
function mobile_web(): bool
{
    $useragent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
    $useragent_commentsblock = preg_match('|\(.*?\)|', $useragent, $matches) > 0 ? $matches[0] : '';

    $mobile_os_list = [
        'Google Wireless Transcoder',
        'Windows CE',
        'WindowsCE',
        'Symbian',
        'Android',
        'armv6l',
        'armv5',
        'Mobile',
        'CentOS',
        'mowser',
        'AvantGo',
        'Opera Mobi',
        'J2ME/MIDP',
        'Smartphone',
        'Go.Web',
        'Palm',
        'iPAQ',
    ];
    $mobile_token_list = [
        'Profile/MIDP',
        'Configuration/CLDC-',
        '160×160',
        '176×220',
        '240×240',
        '240×320',
        '320×240',
        'UP.Browser',
        'UP.Link',
        'SymbianOS',
        'PalmOS',
        'PocketPC',
        'SonyEricsson',
        'Nokia',
        'BlackBerry',
        'Vodafone',
        'BenQ',
        'Novarra-Vision',
        'Iris',
        'NetFront',
        'HTC_',
        'Xda_',
        'SAMSUNG-SGH',
        'Wapaka',
        'DoCoMo',
        'iPhone',
        'iPod',
    ];

    $found_mobile = CheckSubstrs($mobile_os_list, $useragent_commentsblock) || CheckSubstrs($mobile_token_list, $useragent);

    if ($found_mobile) {
        return true;
    }  return false;
}

function CheckSubstrs($substrs, $text)
{
    foreach ($substrs as $substr) {
        if (strpos($text, $substr) !== false) {
            return true;
        }
    }
    return false;
}

/**
 * 检测是否为App
 * @return bool
 */
function is_app()
{
    if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) {
        return true;
    }// 如果有HTTP_X_WAP_PROFILE则一定是移动设备
    // 如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息
    if (isset($_SERVER['HTTP_VIA'])) {
        return stristr($_SERVER['HTTP_VIA'], 'wap') ? true : false;
    }// 找不到为flase,否则为true
    // 脑残法，判断手机发送的客户端标志,兼容性有待提高
    if (isset($_SERVER['HTTP_USER_AGENT'])) {
        $clientkeywords = [
            'nokia',
            'sony',
            'ericsson',
            'mot',
            'samsung',
            'htc',
            'sgh',
            'lg',
            'sharp',
            'sie-',
            'philips',
            'panasonic',
            'alcatel',
            'lenovo',
            'iphone',
            'ipod',
            'blackberry',
            'meizu',
            'android',
            'netfront',
            'symbian',
            'ucweb',
            'windowsce',
            'palm',
            'operamini',
            'operamobi',
            'openwave',
            'nexusone',
            'cldc',
            'midp',
            'wap',
            'mobile',
        ];
        // 从HTTP_USER_AGENT中查找手机浏览器的关键字
        if (preg_match('/(' . implode('|', $clientkeywords) . ')/i', strtolower($_SERVER['HTTP_USER_AGENT']))) {
            return true;
        }
    }
    // 协议法，因为有可能不准确，放到最后判断
    if (isset($_SERVER['HTTP_ACCEPT'])) {
        // 如果只支持wml并且不支持html那一定是移动设备
        // 如果支持wml和html但是wml在html之前则是移动设备
        if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) {
            return true;
        }
    }
    return false;
}

/**
 * 检测身份证号码
 *
 * @return bool
 */
// function check_idcard(string $id) : bool
// {
//    $id = strtoupper($id);
//    $regx = "/(^\d{15}$)|(^\d{17}([0-9]|X)$)/";
//    $arr_split = [];
//    if ( !preg_match($regx, $id) ) return false;
//    if ( 15 == strlen($id) ) { //检查15位
//        $regx = "/^(\d{6})+(\d{2})+(\d{2})+(\d{2})+(\d{3})$/";
//        @preg_match($regx, $id, $arr_split);
//        //检查生日日期是否正确
//        $dtm_birth = "19" . $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4];
//        if ( !strtotime($dtm_birth) ) return false; else return true;
//    } else {      //检查18位
//        $regx = "/^(\d{6})+(\d{4})+(\d{2})+(\d{2})+(\d{3})([0-9]|X)$/";
//        @preg_match($regx, $id, $arr_split);
//        $dtm_birth = $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4];
//        if ( !strtotime($dtm_birth) ) return false;//检查生日日期是否正确
//        else {
//            //检验18位身份证的校验码是否正确。
//            //校验位按照ISO 7064:1983.MOD 11-2的规定生成，X可以认为是数字10。
//            $arr_int = [
//                7,
//                9,
//                10,
//                5,
//                8,
//                4,
//                2,
//                1,
//                6,
//                3,
//                7,
//                9,
//                10,
//                5,
//                8,
//                4,
//                2,
//            ];
//            $arr_ch = [
//                '1',
//                '0',
//                'X',
//                '9',
//                '8',
//                '7',
//                '6',
//                '5',
//                '4',
//                '3',
//                '2',
//            ];
//            $sign = 0;
//            for ($i = 0; $i < 17; $i++) {
//                $b = (int)$id{$i};
//                $w = $arr_int[$i];
//                $sign += $b * $w;
//            }
//            $n = $sign % 11;
//            $val_num = $arr_ch[$n];
//            if ( $val_num != substr($id, 17, 1) ) return false; else return true;//phpfensi.com
//        }
//    }
// }

/**
 * 保留几位小数，向下取，就是直接截断 3 为小数即可。
 * @param float $money_num
 * @param int $length
 * @return bool|string
 */
function round_down_decimal(float $money_num = 0, int $length = 2): bool|string
{
    return substr((string) $money_num, 0, strlen((string) $money_num) - _getFloatLength($money_num) + $length);
}

/**
 * 计算小数部分的长度
 * @param $num
 * @return int
 */
function _getFloatLength($num): int
{
    $count = 0;
    $temp = explode('.', $num);
    if (sizeof($temp) > 1) {
        $decimal = end($temp);
        $count = strlen($decimal);
    }
    return $count;
}

/**
 * 金额插入数据库的时候，单位转换为“分”【具体根据$length长度而定】
 * @param float $money
 * @param int $length
 * @return float
 */
function set_money_conversion(float $money = 0, int $length = 2): float
{
    return floatval($money) * pow(10, $length);
}

/**
 * 金额取出的时候，由“分”转化为“元”【具体根据$length长度而定】
 * @param float $money
 * @param int $length
 * @return float
 */
function get_money_conversion(float $money = 0, int $length = 2): float
{
    return floatval($money) / pow(10, $length);
}

/**
 * 是否为日期格式
 * @param string $date
 * @return bool
 */
function is_date(string $date): bool
{
    $ret = strtotime($date);
    return $ret !== false && $ret != -1;
    // if (date('Y-m-d H:i:s', strtotime($date)) === $date) return true;
    // else return false;
}


function month_list(int $start, int $end): array
{
    if (! is_numeric($start) || ! is_numeric($end) || ($end <= $start)) {
        return '';
    }
    $start = date('Y-m', $start);
    $end = date('Y-m', $end);
    // 转为时间戳
    $start = strtotime($start . '-01');
    $end = strtotime($end . '-01');
    $i = 0;
    $d = [];
    while ($start <= $end) {
        // 这里累加每个月的的总秒数 计算公式：上一月1号的时间戳秒数减去当前月的时间戳秒数
        $d[$i] = trim(date('Y-m', $start), ' ');
        $start += strtotime('+1 month', $start) - $start;
        ++$i;
    }
    return $d;
}

function get_errors_list(array $data = []): string
{
    $html = '';
    if (! empty($data)) {
        foreach ($data as $k => $v) {
            $html .= $k + 1 . '.' . $v . PHP_EOL;
        }
    }
    return $html;
}

// 内存占用空间
function memory_usage()
{
    return (! function_exists('memory_get_usage')) ? '0' : round(memory_get_usage() / 1024 / 1024, 2) . 'MB';
}

/**
 *  参数说明
 *  $string  欲截取的字符串
 *  $sublen  截取的长度
 *  $start   从第几个字节截取，默认为0
 *  $code    字符编码，默认UTF-8.
 * @param mixed $code
 */
function cut_str(string $string, int $sublen = 100, int $start = 0, $code = 'UTF-8')
{
    if ($code == 'UTF-8') {
        $pa = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|\xe0[\xa0-\xbf][\x80-\xbf]|[\xe1-\xef][\x80-\xbf][\x80-\xbf]|\xf0[\x90-\xbf][\x80-\xbf][\x80-\xbf]|[\xf1-\xf7][\x80-\xbf][\x80-\xbf][\x80-\xbf]/";
        preg_match_all($pa, $string, $t_string);
        if (count($t_string[0]) - $start > $sublen) {
            return join('', array_slice($t_string[0], $start, $sublen)) . '.....';
        }
        return join('', array_slice($t_string[0], $start, $sublen));
    }
    $start = $start * 2;
    $sublen = $sublen * 2;
    $strlen = strlen($string);
    $tmpstr = '';
    for ($i = 0; $i < $strlen; ++$i) {
        if ($i >= $start && $i < ($start + $sublen)) {
            if (ord(substr($string, $i, 1)) > 129) {
                $tmpstr .= substr($string, $i, 2);
            } else {
                $tmpstr .= substr($string, $i, 1);
            }
        }
        if (ord(substr($string, $i, 1)) > 129) {
            ++$i;
        }
    }
    if (strlen($tmpstr) < $strlen) {
        $tmpstr .= '';
    }
    return $tmpstr;
}

/**
 * 字符串转16进制
 * @param $string
 * @return string
 */
function strToHex($string)
{
    $this_i = $hex = '';
    for ($i = 0; $i < strlen($string); ++$i) {
        $this_i = dechex(ord($string[$i]));
        if (strlen($this_i) == 0) {
            $this_i = '00';
        } elseif (strlen($this_i) == 1) {
            $this_i = '0' . $this_i;
        }
        $hex .= $this_i;
    }
    return strtoupper($hex);
}

/**
 * 16进制转字符串
 * @param $hex
 * @return string
 */
function hexToStr($hex)
{
    $sendStrArray = str_split(str_replace(' ', '', $hex), 2);  // 将16进制数据转换成两个一组的数组
    $send_info = '';
    for ($j = 0; $j < count($sendStrArray); ++$j) {
        $send_info .= chr(hexdec($sendStrArray[$j]));
    }
    return $send_info;
}

/**
 * crc16 modbus协议算法.
 * @param mixed $string
 */
function crc16($string, int $start_reverse = 0): string
{
    $string = pack('H*', $string);
    $crc = 0xFFFF;
    for ($x = 0; $x < strlen($string); ++$x) {
        $crc = $crc ^ ord($string[$x]);
        for ($y = 0; $y < 8; ++$y) {
            if (($crc & 0x0001) == 0x0001) {
                $crc = (($crc >> 1) ^ 0xA001);
            } else {
                $crc = $crc >> 1;
            }
        }
    }
    $more_data = strlen(dechex((int) floor($crc % 256))) < 2 ? '0' . dechex($crc % 256) : dechex($crc % 256);
    $less_data = strlen(dechex((int) floor($crc / 256))) < 2 ? '0' . dechex($crc / 256) : dechex($crc / 256);
    return strtoupper($start_reverse == 0 ? ($more_data . $less_data) : ($less_data . $more_data));
}

function gencrc16($string): string
{
    $crc = 0xFFFF;
    for ($x = 0; $x < strlen($string); ++$x) {
        $crc = $crc ^ ord($string[$x]);
        for ($y = 0; $y < 8; ++$y) {
            if (($crc & 0x0001) == 0x0001) {
                $crc = (($crc >> 1) ^ 0xA001);
            } else {
                $crc = $crc >> 1;
            }
        }
    }
    return strtoupper((string) $crc);
}

/**
 * @param mixed $lat1
 * @param mixed $lng1
 * @param mixed $lat2
 * @param mixed $lng2
 * @return int
 * 经纬度计算两点之间的距离
 */
function getDistance($lat1, $lng1, $lat2, $lng2)
{
    // 将角度转为狐度
    $radLat1 = deg2rad($lat1); // deg2rad()函数将角度转换为弧度
    $radLat2 = deg2rad($lat2);
    $radLng1 = deg2rad($lng1);
    $radLng2 = deg2rad($lng2);

    $a = $radLat1 - $radLat2;
    $b = $radLng1 - $radLng2;

    return 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2))) * 6378.137;
}

/**
 * @param float $radius 星球半径 KM
 * @param mixed $lat1
 * @param mixed $lon1
 * @param mixed $lat2
 * @param mixed $lon2
 * @return float
 * 经纬度计算两点之间的距离
 */
function distance($lat1, $lon1, $lat2, $lon2, $radius = 6378.137)
{
    $rad = floatval(M_PI / 180.0);

    $lat1 = floatval($lat1) * $rad;
    $lon1 = floatval($lon1) * $rad;
    $lat2 = floatval($lat2) * $rad;
    $lon2 = floatval($lon2) * $rad;

    $theta = $lon2 - $lon1;

    $dist = acos(sin($lat1) * sin($lat2) + cos($lat1) * cos($lat2) * cos($theta));

    if ($dist < 0) {
        $dist += M_PI;
    }
    return $dist = $dist * $radius;
}

function request_function(string $url = '', array $post_data = [], $ispost = true, $json_conversion = 1)
{
    if (empty($url)) {
        return false;
    }
    $o = '';
    if (! empty($post_data)) {
        foreach ($post_data as $k => $v) {
            $o .= "{$k}=" . urlencode($v) . '&';
        }
        $post_data = substr($o, 0, -1);
        $key = md5(base64_encode($post_data));
    } else {
        $key = 'key';
    }
    if ($ispost) {
        $url = $url;
        $curlPost = $post_data;
    } else {
        $url = $url . '?' . $post_data;
        $curlPost = 'key=' . $key;
    }
    $ch = curl_init(); // 初始化curl
    curl_setopt($ch, CURLOPT_URL, $url); // 抓取指定网页
    curl_setopt($ch, CURLOPT_HEADER, 0); // 设置header
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 要求结果为字符串且输出到屏幕上
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);    // 禁止 cURL 验证对等证书
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);    // 是否检测服务器的域名与证书上的是否一致
    if ($ispost) {
        curl_setopt($ch, CURLOPT_POST, 1); // post提交方式
        curl_setopt($ch, CURLOPT_POSTFIELDS, $curlPost);
    }
    $data = curl_exec($ch); // 运行curl
    curl_close($ch);
    return $json_conversion ? json_decode($data, true) : $data;
}

/**
 * 数据类型过滤
 * @param $field
 * @param $field_type
 * @param $default_val
 * @return float|int|mixed|string|void
 */
function set_field_filtering($field = '', $field_type = 'string', $default_val = '')
{
    if (isset($field) || $field == null) {
        return $field;
    }
    $field_type = strtolower(trim($field_type));
    if (in_array($field_type, [
        'str',
        'string',
        'varchar',
    ])) {
        return trim($field ?? $default_val);
    }
    if (in_array($field_type, [
        'int',
        'intval',
        'number',
    ])) {
        return intval($field ?? $default_val);
    }
    if (in_array($field_type, [
        'double',
        'float',
        'floatval',
    ])) {
        return floatval($field ?? $default_val);
    }
}

function get_server_url(): string
{
    $pact = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https://' : 'http://';
    return $pact . ($_SERVER['SERVER_NAME'] ?? '');
}

function set_server_url($str): string
{
    return get_server_url() . $str;
    // return get_server_url() . $str;
}

function remove_server_url(string $img): string
{
    return str_replace(get_server_url(), '', $img);
}

// 生成随机数
function get_rand($sum = 6)
{
    $rand = '';
    for ($i = 1; $i <= $sum; ++$i) {
        $rand .= rand(0, 9);
    }
    return $rand;
}

/*
 * 获取随机字符串
 *
 * @param  int  $randLength     长度
 * @param  int  $create_time    是否加入当前时间戳
 * @param  int  $includenumber  是否包含数字
 *
 * @return string
 */
if (! function_exists('rand_str')) {
    function rand_str($randLength = 6, $create_time = 1, $includenumber = 1)
    {
        if ($includenumber) {
            $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789';
        } else {
            $chars = 'abcdefghijklmnopqrstuvwxyz';
        }
        $len = strlen($chars);
        $randStr = '';
        for ($i = 0; $i < $randLength; ++$i) {
            $randStr .= $chars[mt_rand(0, $len - 1)];
        }
        $tokenvalue = $randStr;
        if ($create_time) {
            $tokenvalue = $randStr . '-' . time();
        }
        return $tokenvalue;
    }
}

/**
 * 倒计时 转化成 天-时分秒 展示.
 *
 * @param mixed $time
 * @return string
 */
function countdown_conversion_time($time)
{
    if (empty($time)) {
        return '';
    }
    $day = intval($time / (60 * 60 * 24));
    $hour = intval($time / (60 * 60) - $day * 24);
    $minute = intval($time / 60 - $day * 24 * 60 - $hour * 60);
    $second = intval($time - $day * 24 * 60 * 60 - $hour * 60 * 60 - $minute * 60);

    $day = (intval($day) <= 0) ? '' : $day . '天 ';
    if (intval($hour) <= 9) {
        $hour = '0' . $hour;
    }
    if (intval($minute) <= 9) {
        $minute = '0' . $minute;
    }
    if (intval($second) <= 9) {
        $second = '0' . $second;
    }
    return $day . $hour . '时' . $minute . '分' . $second . '秒';
}

function amount_conversion($money, $length = 2)
{
    return round(floatval($money), $length);
}

/**
 * 金额格式化.
 *
 * @param   [float]         $value     [金额]
 * @param   [int]           $decimals  [保留的位数]
 * @param   [string]        $dec_point [保留小数分隔符]
 */
function PriceNumberFormat($value, $decimals = 2, $dec_point = '.')
{
    return number_format($value, $decimals, $dec_point, '');
}

function getWxPayMoeny($moeny)
{
    return $moeny * 100; // 以分为单位
}

/*
 * 检测银行卡
  16-19 位卡号校验位采用 Luhm 校验方法计算：
    1，将未带校验位的 15 位卡号从右依次编号 1 到 15，位于奇数位号上的数字乘以 2
    2，将奇位乘积的个十位全部相加，再加上所有偶数位上的数字
    3，将加法和加上校验位能被 10 整除。
*/
function check_bank_card($bankCardNo)
{
    $strlen = strlen($bankCardNo);
    if ($strlen < 15 || $strlen > 19) {
        return false;
    }
    if (! preg_match('/^\d{15}$/i', $bankCardNo) && ! preg_match('/^\d{16}$/i', $bankCardNo) && ! preg_match('/^\d{17}$/i', $bankCardNo) && ! preg_match('/^\d{18}$/i', $bankCardNo) && ! preg_match('/^\d{19}$/i', $bankCardNo)) {
        return false;
    }
    $arr_no = str_split($bankCardNo);
    $last_n = $arr_no[count($arr_no) - 1];
    krsort($arr_no);
    $i = 1;
    $total = 0;
    foreach ($arr_no as $n) {
        if ($i % 2 == 0) {
            $ix = $n * 2;
            if ($ix >= 10) {
                $nx = 1 + ($ix % 10);
                $total += $nx;
            } else {
                $total += $ix;
            }
        } else {
            $total += $n;
        }
        ++$i;
    }
    $total -= $last_n;
    $x = 10 - ($total % 10);
    if ($x != $last_n) {
        return false;
    }
    return true;
}

/**
 * 生成混合code.
 * @param int $length
 * @return string
 */
function make_blend_code(int $length = 20): string
{
    $chars = [
        'a',
        'b',
        'c',
        'd',
        'e',
        'f',
        'g',
        'h',
        'i',
        'j',
        'k',
        'l',
        'm',
        'n',
        'o',
        'p',
        'q',
        'r',
        's',
        't',
        'u',
        'v',
        'w',
        'x',
        'y',
        'z',
        'A',
        'B',
        'C',
        'D',
        'E',
        'F',
        'G',
        'H',
        'I',
        'J',
        'K',
        'L',
        'M',
        'N',
        'O',
        'P',
        'Q',
        'R',
        'S',
        'T',
        'U',
        'V',
        'W',
        'X',
        'Y',
        'Z',
        '0',
        '1',
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
    ];
    $array = array_rand($chars, $length);
    $rand = '';
    for ($i = 0; $i < $length; ++$i) {
        $rand .= $chars[$array[$i]];
    }
    return $rand;
}

/*
 * 生成UUID
 *
 * @param  string  $string
 *
 * @return string
 */
if (! function_exists('make_uuid')) {
    /**
     * 生成UUID.
     */
    function make_uuid(string $string = ''): string
    {
        $string = $string === '' ? uniqid((string) mt_rand(), true) : ((int) preg_match('/[A-Z]/', $string) === 0 ? $string : mb_strtolower($string, 'UTF-8'));
        $code = hash('sha1', $string . ':UUID');
        $uuid = substr($code, 0, 10);
        $uuid .= substr($code, 10, 4);
        $uuid .= substr($code, 16, 4);
        $uuid .= substr($code, 22, 4);
        $uuid .= substr($code, 28, 12);
        $uuid = strtoupper($uuid);
        unset($string, $code);
        return $uuid;
    }
}

/**
 * Generate UUID (string hash based).
 */
function get_uuid(string $string = ''): string
{
    if ($string === '') {
        // Create random string
        $string = uniqid(microtime() . getmypid() . mt_rand(), true);
    }

    $start = 0;
    $codes = [];
    $length = [
        8,
        4,
        4,
        4,
        12,
    ];
    $string = hash('md5', $string);

    foreach ($length as $len) {
        $codes[] = substr($string, $start, $len);
        $start += $len;
    }

    $uuid = implode('-', $codes);

    unset($string, $start, $codes, $length, $len);
    return $uuid;
}

// function getFirstChar($s)
// {
//    $s0 = mb_substr($s, 0, 3); //获取名字的姓
//    $s = iconv('UTF-8', 'gb2312', $s0); //将UTF-8转换成GB2312编码
//    if ( ord($s0) > 128 ) { //汉字开头，汉字没有以U、V开头的
//        $asc = ord($s{0}) * 256 + ord($s{1}) - 65536;
//        if ( $asc >= -20319 and $asc <= -20284 ) return "A";
//        if ( $asc >= -20283 and $asc <= -19776 ) return "B";
//        if ( $asc >= -19775 and $asc <= -19219 ) return "C";
//        if ( $asc >= -19218 and $asc <= -18711 ) return "D";
//        if ( $asc >= -18710 and $asc <= -18527 ) return "E";
//        if ( $asc >= -18526 and $asc <= -18240 ) return "F";
//        if ( $asc >= -18239 and $asc <= -17760 ) return "G";
//        if ( $asc >= -17759 and $asc <= -17248 ) return "H";
//        if ( $asc >= -17247 and $asc <= -17418 ) return "I";
//        if ( $asc >= -17417 and $asc <= -16475 ) return "J";
//        if ( $asc >= -16474 and $asc <= -16213 ) return "K";
//        if ( $asc >= -16212 and $asc <= -15641 ) return "L";
//        if ( $asc >= -15640 and $asc <= -15166 ) return "M";
//        if ( $asc >= -15165 and $asc <= -14923 ) return "N";
//        if ( $asc >= -14922 and $asc <= -14915 ) return "O";
//        if ( $asc >= -14914 and $asc <= -14631 ) return "P";
//        if ( $asc >= -14630 and $asc <= -14150 ) return "Q";
//        if ( $asc >= -14149 and $asc <= -14091 ) return "R";
//        if ( $asc >= -14090 and $asc <= -13319 ) return "S";
//        if ( $asc >= -13318 and $asc <= -12839 ) return "T";
//        if ( $asc >= -12838 and $asc <= -12557 ) return "W";
//        if ( $asc >= -12556 and $asc <= -11848 ) return "X";
//        if ( $asc >= -11847 and $asc <= -11056 ) return "Y";
//        if ( $asc >= -11055 and $asc <= -10247 ) return "Z";
//    } elseif ( ord($s) >= 48 and ord($s) <= 57 ) { //数字开头
//        switch (iconv_substr($s, 0, 1, 'utf-8')) {
//            case 1:
//                return "Y";
//            case 2:
//                return "E";
//            case 3:
//                return "S";
//            case 4:
//                return "S";
//            case 5:
//                return "W";
//            case 6:
//                return "L";
//            case 7:
//                return "Q";
//            case 8:
//                return "B";
//            case 9:
//                return "J";
//            case 0:
//                return "L";
//        }
//    } elseif ( ord($s) >= 65 and ord($s) <= 90 ) { //大写英文开头
//        return substr($s, 0, 1);
//    } elseif ( ord($s) >= 97 and ord($s) <= 122 ) { //小写英文开头
//        return strtoupper(substr($s, 0, 1));
//    } else {
//        return iconv_substr($s0, 0, 1, 'utf-8');
//        //中英混合的词语，不适合上面的各种情况，因此直接提取首个字符即可
//    }
// }

function strip_html_tags($tags, $str)
{
    $html = [];
    foreach ($tags as $tag) {
        $html[] = '/(<(?:\/' . $tag . '|' . $tag . ')[^>]*>)/i';
    }
    return preg_replace($html, '', $str);
}

// 获取本地存储的文件
function GetLocalFileByPath($path)
{
    return asset('uploads') . '/' . $path;
}

// 参数1：访问的URL，参数2：post数据(不填则为GET)，参数3：提交的$cookies,参数4：是否返回$cookies
function curl_request($url, $post = '', $json = false, $header = [])
{
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)');
    curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 0);
    curl_setopt($curl, CURLOPT_AUTOREFERER, 0);
    // curl_setopt($curl, CURLOPT_REFERER, "http://XXX");
    if ($json) {
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($post));
        curl_setopt($curl, CURLOPT_HTTPHEADER, array_merge([
            'Content-Type: application/json; charset=utf-8',
            // 伪造IP
            // 'CLIENT-IP:85.25.105.77','X-FORWARDED-FOR:85.25.105.77',//此处可以改为任意假IP
            'Content-Length: ' . (empty($post) ? 0 : strlen(http_build_query($post))),
        ], $header));
    }
    curl_setopt($curl, CURLOPT_TIMEOUT, 60);
    // 不输出头信息
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    $data = curl_exec($curl);
    if (curl_errno($curl)) {
        return false;
        // throw new Exception("Error:".curl_error($curl));
    }
    curl_close($curl);
    return $data;
}

function curlRequest($url, $params = [], $method = 'POST', $header = [], $type = 'json', $options = [])
{
    if (empty($options)) {
        $options = [
            CURLOPT_CONNECTTIMEOUT => 30,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_HTTPHEADER => ['X-REQUESTED-WITH: XMLHttpRequest'],
        ];
    }

    $method = strtoupper($method);
    $protocol = substr($url, 0, 5);

    if ($type == 'json' && is_array($params)) {
        $query_string = json_encode($params, JSON_UNESCAPED_UNICODE);
    } else {
        $query_string = is_array($params) ? http_build_query($params) : $params;
    }

    $ch = curl_init();
    $defaults = [];
    if ($method == 'GET') {
        $geturl = $query_string ? $url . (stripos($url, '?') !== false ? '&' : '?') . $query_string : $url;
        $defaults[CURLOPT_URL] = $geturl;
    } else {
        $defaults[CURLOPT_URL] = $url;
        if ($method == 'POST') {
            $defaults[CURLOPT_POST] = 1;
        } else {
            $defaults[CURLOPT_CUSTOMREQUEST] = $method;
        }
        $defaults[CURLOPT_POSTFIELDS] = $query_string;
    }

    $defaults[CURLOPT_HEADER] = false;
    $defaults[CURLOPT_USERAGENT] = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.98 Safari/537.36';
    $defaults[CURLOPT_FOLLOWLOCATION] = true;
    $defaults[CURLOPT_RETURNTRANSFER] = true;
    $defaults[CURLOPT_CONNECTTIMEOUT] = 3;
    $defaults[CURLOPT_TIMEOUT] = 3;

    // disable 100-continue
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']);

    if ($protocol == 'https') {
        $defaults[CURLOPT_SSL_VERIFYPEER] = false;
        $defaults[CURLOPT_SSL_VERIFYHOST] = false;
    }

    curl_setopt_array($ch, (array) $options + $defaults);

    curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge([
        'Content-Type: application/json; charset=utf-8',
        // 伪造IP
        'CLIENT-IP:85.25.105.77',
        'X-FORWARDED-FOR:85.25.105.77',
        // 此处可以改为任意假IP
        'Content-Length: ' . strlen($query_string),
    ], $header));

    $ret = curl_exec($ch);
    $err = curl_error($ch);

    curl_close($ch);

    return json_decode($ret, true);
}

// 过滤值为空的数组
function filterEmptyArr($array)
{
    if (! is_array($array)) {
        return false;
    }

    $return_arr = [];
    foreach ($array as $k => $v) {
        if (is_array($v)) {
            foreach ($v as $k1 => $v1) {
                if ($v1) {
                    $return_arr[$k] = $v;
                }
            }
        } elseif ($v) {
            $return_arr[$k] = $v;
        }
    }

    return $return_arr;
}

// 验证数组是否为空,除了指定的key
function CheckArrIsEmpty($arr, $except = [])
{
    if (! is_array($arr)) {
        return false;
    }
    foreach ($arr as $k => $value) {
        if (is_array($value)) {
            CheckArrIsEmpty($value);
        } elseif (! $value && $value != 0) {
            if (! in_array($k, $except)) {
                throw new Exception($k . ' is empty.', 40000);
            }
        }
    }
}

/**
 * 获取客户端浏览器信息.
 * @return string
 */
function get_broswer()
{
    $sys = $_SERVER['HTTP_USER_AGENT'];  // 获取用户代理字符串
    if (stripos($sys, 'Firefox/') > 0) {
        preg_match('/Firefox\/([^;)]+)+/i', $sys, $b);
        $exp[0] = 'Firefox';
        $exp[1] = $b[1];    // 获取火狐浏览器的版本号
    } elseif (stripos($sys, 'Maxthon') > 0) {
        preg_match('/Maxthon\/([\d\.]+)/', $sys, $aoyou);
        $exp[0] = '傲游';
        $exp[1] = $aoyou[1];
    } elseif (stripos($sys, 'MSIE') > 0) {
        preg_match('/MSIE\s+([^;)]+)+/i', $sys, $ie);
        $exp[0] = 'IE';
        $exp[1] = $ie[1];  // 获取IE的版本号
    } elseif (stripos($sys, 'OPR') > 0) {
        preg_match('/OPR\/([\d\.]+)/', $sys, $opera);
        $exp[0] = 'Opera';
        $exp[1] = $opera[1];
    } elseif (stripos($sys, 'Edge') > 0) {
        // win10 Edge浏览器 添加了chrome内核标记 在判断Chrome之前匹配
        preg_match('/Edge\/([\d\.]+)/', $sys, $Edge);
        $exp[0] = 'Edge';
        $exp[1] = $Edge[1];
    } elseif (stripos($sys, 'Chrome') > 0) {
        preg_match('/Chrome\/([\d\.]+)/', $sys, $google);
        $exp[0] = 'Chrome';
        $exp[1] = $google[1];  // 获取google chrome的版本号
    } elseif (stripos($sys, 'rv:') > 0 && stripos($sys, 'Gecko') > 0) {
        preg_match('/rv:([\d\.]+)/', $sys, $IE);
        $exp[0] = 'IE';
        $exp[1] = $IE[1];
    } else {
        $exp[0] = '未知浏览器';
        $exp[1] = '';
    }
    return $exp[0] . '(' . $exp[1] . ')';
}

/**
 * 获取客户端操作系统信息,包括win10.
 * @return string
 */
function get_os(): string
{
    $agent = $_SERVER['HTTP_USER_AGENT'];
    $os = false;
    if (preg_match('/win/i', $agent) && strpos($agent, '95')) {
        $os = 'Windows 95';
    } elseif (preg_match('/win 9x/i', $agent) && strpos($agent, '4.90')) {
        $os = 'Windows ME';
    } elseif (preg_match('/win/i', $agent) && preg_match('/98/i', $agent)) {
        $os = 'Windows 98';
    } elseif (preg_match('/win/i', $agent) && preg_match('/nt 6.0/i', $agent)) {
        $os = 'Windows Vista';
    } elseif (preg_match('/win/i', $agent) && preg_match('/nt 6.1/i', $agent)) {
        $os = 'Windows 7';
    } elseif (preg_match('/win/i', $agent) && preg_match('/nt 6.2/i', $agent)) {
        $os = 'Windows 8';
    } elseif (preg_match('/win/i', $agent) && preg_match('/nt 10.0/i', $agent)) {
        $os = 'Windows 10'; # 添加win10判断
    } elseif (preg_match('/win/i', $agent) && preg_match('/nt 5.1/i', $agent)) {
        $os = 'Windows XP';
    } elseif (preg_match('/win/i', $agent) && preg_match('/nt 5/i', $agent)) {
        $os = 'Windows 2000';
    } elseif (preg_match('/win/i', $agent) && preg_match('/nt/i', $agent)) {
        $os = 'Windows NT';
    } elseif (preg_match('/win/i', $agent) && preg_match('/32/i', $agent)) {
        $os = 'Windows 32';
    } elseif (preg_match('/linux/i', $agent)) {
        $os = 'Linux';
    } elseif (preg_match('/unix/i', $agent)) {
        $os = 'Unix';
    } elseif (preg_match('/sun/i', $agent) && preg_match('/os/i', $agent)) {
        $os = 'SunOS';
    } elseif (preg_match('/ibm/i', $agent) && preg_match('/os/i', $agent)) {
        $os = 'IBM OS/2';
    } elseif (preg_match('/Mac/i', $agent) && preg_match('/PC/i', $agent)) {
        $os = 'Macintosh';
    } elseif (preg_match('/PowerPC/i', $agent)) {
        $os = 'PowerPC';
    } elseif (preg_match('/AIX/i', $agent)) {
        $os = 'AIX';
    } elseif (preg_match('/HPUX/i', $agent)) {
        $os = 'HPUX';
    } elseif (preg_match('/NetBSD/i', $agent)) {
        $os = 'NetBSD';
    } elseif (preg_match('/BSD/i', $agent)) {
        $os = 'BSD';
    } elseif (preg_match('/OSF1/i', $agent)) {
        $os = 'OSF1';
    } elseif (preg_match('/IRIX/i', $agent)) {
        $os = 'IRIX';
    } elseif (preg_match('/FreeBSD/i', $agent)) {
        $os = 'FreeBSD';
    } elseif (preg_match('/teleport/i', $agent)) {
        $os = 'teleport';
    } elseif (preg_match('/flashget/i', $agent)) {
        $os = 'flashget';
    } elseif (preg_match('/webzip/i', $agent)) {
        $os = 'webzip';
    } elseif (preg_match('/offline/i', $agent)) {
        $os = 'offline';
    } else {
        $os = '未知操作系统';
    }
    return $os;
}

// 生成树形结构数据

function listToTree($list, $pk = 'id', $pid = 'pid', $child = '_child', $root = 0)
{
    $tree = [];
    if (is_array($list)) {
        $refer = [];
        foreach ($list as $key => $data) {
            $refer[$data[$pk]] = &$list[$key];
        }

        foreach ($list as $key => $data) {
            // 判断是否存在parent
            $parentId = $data[$pid];

            if ($root == $parentId) {
                $tree[$data[$pk]] = &$list[$key];
            } else {
                if (isset($refer[$parentId])) {
                    $parent = &$refer[$parentId];
                    $parent[$child][$data[$pk]] = &$list[$key];
                }
            }
        }
    }

    return $tree;
}



function excelTime($date, $time = false)
{
    if (function_exists('GregorianToJD')) {
        if (is_numeric($date)) {
            $jd = gregoriantojd(1, 1, 1970);
            $gregorian = jdtogregorian($jd + intval($date) - 25569);
            $date = explode('/', $gregorian);
            return str_pad($date[2], 4, '0', STR_PAD_LEFT) . '-' . str_pad($date[0], 2, '0', STR_PAD_LEFT) . '-' . str_pad($date[1], 2, '0', STR_PAD_LEFT) . ($time ? ' 00:00:00' : '');
        }
    } else {
        $date = $date > 25568 ? $date + 1 : 25569;
        /* There was a bug if Converting date before 1-1-1970 (tstamp 0) */
        $ofs = (70 * 365 + 17 + 2) * 86400;
        $date = date('Y-m-d', ($date * 86400) - $ofs) . ($time ? ' 00:00:00' : '');
    }
    return $date;
}

// 判断是否是微信浏览器
function is_weixin(): bool
{
    if (strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false) {
        return true;
    }
    return false;
}

// 是否为手机号码
if (! function_exists('is_mobile')) {
    function is_mobile(string $text)
    {
        $search = '/^0?1[3|4|5|6|7|8][0-9]\d{8}$/';
        if (preg_match($search, $text)) {
            return true;
        }  return false;
    }
}

// 手机号码 中间4位加密
if (! function_exists('get_encryption_mobile')) {
    function get_encryption_mobile($tel)
    {
        return preg_replace('/(\d{3})\d{4}(\d{4})/', '$1****$2', $tel);
    }
}

/*
 * API统一的数据返回格式
 */
if (! function_exists('return_api_format')) {
    function return_api_format($return = []): false|string
    {
        $return['data'] = ! isset($return['data']) ? [] : $return['data'];
        $return['msg'] = ! isset($return['msg']) ? '获取成功' : $return['msg'];
        $return['status'] = ! isset($return['status']) ? (empty($return['data']) ? 40000 : 200) : $return['status'];
        return json_encode($return, JSON_UNESCAPED_UNICODE);
    }
}

if (! function_exists('del_dir_files')) {
    /**
     * 删除文件夹与下方的所有文件.
     *
     * @param string $dirName 文件夹名称
     * @param int $delete_dir 是否删除文件夹【1.删除；0.不删除】
     */
    function del_dir_files(string $dirName, int $delete_dir = 1)
    {
        if ($handle = @opendir($dirName)) {
            while (false !== ($item = @readdir($handle))) {
                if ($item != '.' && $item != '..') {
                    if (is_dir($dirName . '/' . $item)) {
                        del_dir_files($dirName . '/' . $item);
                    } else {
                        @unlink($dirName . '/' . $item);
                    }
                }
            }
            @closedir($handle);
        }
        if ($delete_dir == 1) {
            @rmdir($dirName);
        }
    }
}

if (! function_exists('get_file_filtering')) {
    /**
     * 获取指定格式的文件.
     *
     * @param array $array
     * @param array $format
     *
     * @return array
     */
    function get_file_filtering($array = [], $format = [])
    {
        $return = [];
        if (empty($array) || empty($format)) {
            return $return;
        }
        foreach ($array as $key => $value) {
            $arr = pathinfo($value);
            if (! empty($arr['extension']) && in_array($arr['extension'], $format)) {
                $return[] = $value;
            }
        }
        return $return;
    }
}

if (! function_exists('write_lock_file')) {
    /**
     * 写入锁文件.
     * @param mixed $path
     */
    function write_lock_file($path, string $content = ''): void
    {
        $lock_file = fopen($path . '/lock', 'w+'); // 创建 锁文件
        fwrite($lock_file, empty($content) ? date('Y-m-d H:i:s') : $content); // 写入
    }
}

if (! function_exists('del_dir_files')) {
    /**
     * 删除文件夹与下方的所有文件.
     * @param string $dirName 文件夹名称
     * @param int $delete_dir 是否删除文件夹【1.删除；0.不删除】
     */
    function del_dir_files(string $dirName, int $delete_dir = 1): void
    {
        if ($handle = @opendir($dirName)) {
            while (false !== ($item = @readdir($handle))) {
                if ($item != '.' && $item != '..') {
                    if (is_dir($dirName . '/' . $item)) {
                        del_dir_files($dirName . '/' . $item);
                    } else {
                        @unlink($dirName . '/' . $item);
                    }
                }
            }
            @closedir($handle);
        }
        if ($delete_dir == 1) {
            @rmdir($dirName);
        }
    }
}

if (! function_exists('get_client_info')) {
    /**
     * 获取IP与浏览器信息、语言
     */
    function get_client_info(): array
    {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $XFF = $_SERVER['HTTP_X_FORWARDED_FOR'];
            $client_pos = strpos($XFF, ', ');
            $client_ip = $client_pos !== false ? substr($XFF, 0, $client_pos) : $XFF;
            unset($XFF, $client_pos);
        } else {
            $client_ip = $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? $_SERVER['LOCAL_ADDR'] ?? '0.0.0.0';
        }
        $client_lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 5) : '';
        $client_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        return [
            'ip' => &$client_ip,
            'lang' => &$client_lang,
            'agent' => &$client_agent,
        ];
    }
}

if (! function_exists('get_ip')) {
    function get_ip(): string
    {
        $data = get_client_info();
        return empty($data['ip']) ? '' : $data['ip'];
    }
}

if (! function_exists('get_month_days')) {
    /**
     * 获取某月份的所有日期列表.
     *
     * @param string $time
     * @param string $format
     *
     * @return array
     */
    function get_month_days($time = '', $format = 'Y-m-d')
    {
        $time = $time != '' ? $time : time();
        // 获取当前周几
        $week = date('d', $time);
        $date = [];
        for ($i = 1; $i <= date('t', $time); ++$i) {
            $date[$i] = date($format, strtotime('+' . ($i - $week) . ' days', $time));
        }
        return $date;
    }
}

if (! function_exists('get_month_range')) {
    /**
     * 指定日期范围之内的所有月份.
     *
     * @param string $start_date 开始日期
     * @param string $end_date 结束日期
     * @param string $format 返回格式
     *
     * @return array
     */
    function get_month_range(string $start_date, string $end_date, string $format = 'Y-m')
    {
        $end = date($format, strtotime($end_date)); // 转换为月
        $range = [];
        $i = 0;
        do {
            $month = date($format, strtotime($start_date . ' + ' . $i . ' month'));
            $range[] = $month;
            ++$i;
        } while ($month < $end);
        return $range;
    }
}

if (! function_exists('get_days_range')) {
    /**
     * 指定日期范围之内的所有天.
     *
     * @param string $start_date 开始日期
     * @param string $end_date 结束日期
     * @param string $format 返回格式
     *
     * @return array
     */
    function get_days_range(string $start_date, string $end_date, string $format = 'Y-m-d')
    {
        $end = date($format, strtotime($end_date)); // 转换为月
        $range = [];
        $i = 0;
        do {
            $day = date($format, strtotime($start_date . ' + ' . $i . ' day'));
            $range[] = $day;
            ++$i;
        } while ($day < $end);
        return $range;
    }
}

if (! function_exists('get_years_range')) {
    /**
     * 指定日期范围之内的所有年份.
     *
     * @param string $start_date 开始日期
     * @param string $end_date 结束日期
     * @param string $format 返回格式
     *
     * @return array
     */
    function get_years_range(string $start_date, string $end_date, string $format = 'Y')
    {
        $start_date = date('Y-m-d', strtotime($start_date . '-01-01'));
        $end = date($format, strtotime($end_date)); // 转换为年
        $range = [];
        $i = 0;
        do {
            $year = date($format, strtotime($start_date . ' + ' . $i . ' year'));
            $range[] = $year;
            ++$i;
        } while ($year < $end);
        return $range;
    }
}

if (! function_exists('set_month_format')) {
    /**
     * 设置 月份 的格式统一
     *
     * @param mixed $month
     * @return string
     */
    function set_month_format($month)
    {
        return (string) (strlen($month) == 1 ? '0' . $month : $month);
    }
}

if (! function_exists('get_mail_template_message')) {
    /**
     * 获取模板消息内容.
     *
     * @param string $change 也可以是数组
     * @param string $code 对应数组
     * @param mixed $content
     *
     * @return mixed
     */
    function get_mail_template_message($content, $change = '', $code = '{$code}')
    {
        return str_replace($code, $change, $content);
    }
}

/**
 * 检测邮箱格式.
 */
function is_email(string $email): bool
{
    // '/^[a-z0-9]+([._-][a-z0-9]+)*@([0-9a-z]+\.[a-z]{2,14}(\.[a-z]{2})?)$/i';
    $checkmail = "/\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*/"; // 定义正则表达式
    if (preg_match($checkmail, $email)) {
        return true;
    }  return false;
}

/**
 * 检测手机号格式是否正确.
 */
function is_mobile(string $text): bool
{
    $search = '/^0?1[345678][0-9]\d{8}$/';
    if (preg_match($search, $text)) {
        return true;
    }  return false;
}

/**
 * 检测URL地址格式.
 */
function check_url(string $_url): bool
{
    $str = "/^http(s?):\\/\\/(?:[A-za-z0-9-]+\\.)+[A-za-z]{2,4}(?:[\\/\\?#][\\/=\\?%\\-&~`@[\\]\\':+!\\.#\\w]*)?$/";
    if (! preg_match($str, $_url)) {
        return false;
    }  return true;
}

/**
 * 保留几位小数，向下取，就是直接截断 3 为小数即可.
 */
function roundDownDecimal(float $money_num = 0, int $length = 2): bool|string
{
    return substr((string) $money_num, 0, strlen((string) $money_num) - _getFloatLength($money_num) + $length);
}

if (! function_exists('writeLog')) {
    function writeLog($msg, $file_name, $path = __DIR__): void
    {
        /**
         * 第一部分路径.
         */
        $dirPath = $path . '/logs';
        if (! is_dir($dirPath)) {
            @mkdir($dirPath);
        }
        $dirPath .= '/' . date('Y');
        if (! is_dir($dirPath)) {
            @mkdir($dirPath);
        }
        $dirPath .= '/' . date('n');
        if (! is_dir($dirPath)) {
            @mkdir($dirPath);
        }
        /*
         * 第二部分
         */
        file_put_contents($dirPath . '/' . (empty($file_name) ? date('j') : $file_name) . '.txt', "\n\n" . date('Y-m-d H:i:s') . "\n", FILE_APPEND);
        file_put_contents($dirPath . '/' . (empty($file_name) ? date('j') : $file_name) . '.txt', print_r($msg, true), FILE_APPEND);
    }
}

/**
 * 倒计时 转化成 天-时分秒 展示.
 * @param mixed $time
 */
function countdownConversionTime($time): string
{
    if (empty($time)) {
        return '';
    }
    $day = intval($time / (60 * 60 * 24));
    $hour = intval($time / (60 * 60) - $day * 24);
    $minute = intval($time / 60 - $day * 24 * 60 - $hour * 60);
    $second = intval($time - $day * 24 * 60 * 60 - $hour * 60 * 60 - $minute * 60);

    $day = (intval($day) <= 0) ? '' : $day . '天 ';
    if (intval($hour) <= 9) {
        $hour = '0' . $hour;
    }
    if (intval($minute) <= 9) {
        $minute = '0' . $minute;
    }
    if (intval($second) <= 9) {
        $second = '0' . $second;
    }
    return $day . $hour . ':' . $minute . ':' . $second . '';
}

if (! function_exists('array_key_first')) {
    /**
     * Gets the first key of an array.
     *
     * @return mixed
     */
    function array_key_first(array $array)
    {
        if (count($array)) {
            reset($array);
            return key($array);
        }
        return null;
    }
}

function hidden_mobile(string $text): string
{
    $start = substr($text, 0, 3);
    $end = substr($text, -4, 4);
    return $start . ' **** ' . $end;
}

function msubstr($str, $start = 0, $length = 200, $charset = 'utf-8', $suffix = true)
{
    if (function_exists('mb_substr')) {
        if ($suffix) {
            return mb_substr($str, $start, $length, $charset) . '...';
        }
        return mb_substr($str, $start, $length, $charset);
    }
    if (function_exists('iconv_substr')) {
        if ($suffix) {
            return iconv_substr($str, $start, $length, $charset) . '...';
        }
        return iconv_substr($str, $start, $length, $charset);
    }
    $re['utf-8'] = '/[x01-x7f]|[xc2-xdf][x80-xbf]|[xe0-xef]
[x80-xbf]{2}|[xf0-xff][x80-xbf]{3}/';
    $re['gb2312'] = '/[x01-x7f]|[xb0-xf7][xa0-xfe]/';
    $re['gbk'] = '/[x01-x7f]|[x81-xfe][x40-xfe]/';
    $re['big5'] = '/[x01-x7f]|[x81-xfe]([x40-x7e]|xa1-xfe])/';
    preg_match_all($re[$charset], $str, $match);
    $slice = join('', array_slice($match[0], $start, $length));
    if ($suffix) {
        return $slice . '…';
    }
    return $slice;
}

// function hidden_bank(string $text, $start = 1): string
// {
//     $start = substr($text, 0, 6);
//     $end = substr($text, -4, 4);
//     return $start . ($start == 1 ? ' ' : '') . '········' . ($start == 1 ? ' ' : '') . $end;
//     return $start . ($start == 1 ? ' ' : '') . '**** ****' . ($start == 1 ? ' ' : '') . $end;
// }

function hidden_bank(string $text): string
{
    $start = substr($text, 0, 4);
    $end = substr($text, -4, 4);
    return $start . '&nbsp;****&nbsp;****&nbsp' . $end;
}

function get_month_first_day(): string
{
    return date('Y-m-01', strtotime(date('Y-m-d', time())));
}

function get_month_last_day(): string
{
    $date = date('Y-m-01', strtotime(date('Y-m-d', time())));
    return date('Y-m-d', strtotime("{$date} +1 month -1 day"));
}

function Xml_Array($xmlstring)
{
    return json_decode(json_encode((array) simplexml_load_string($xmlstring)), true);
}

/**
 * 是否是手机访问
 * @return bool
 */
function IsMobile()
{
    /* 如果有HTTP_X_WAP_PROFILE则一定是移动设备 */
    if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) {
        return true;
    }

    /* 此条摘自TPM智能切换模板引擎，适合TPM开发 */
    if (isset($_SERVER['HTTP_CLIENT']) && $_SERVER['HTTP_CLIENT'] == 'PhoneClient') {
        return true;
    }

    /* 如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息 */
    if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], 'wap') !== false) {
        return true;
    }

    /* 判断手机发送的客户端标志,兼容性有待提高 */
    if (isset($_SERVER['HTTP_USER_AGENT'])) {
        $clientkeywords = [
            'nokia',
            'sony',
            'ericsson',
            'mot',
            'samsung',
            'htc',
            'sgh',
            'lg',
            'sharp',
            'sie-',
            'philips',
            'panasonic',
            'alcatel',
            'lenovo',
            'iphone',
            'ipad',
            'ipod',
            'blackberry',
            'meizu',
            'android',
            'netfront',
            'symbian',
            'ucweb',
            'windowsce',
            'palm',
            'operamini',
            'operamobi',
            'openwave',
            'nexusone',
            'cldc',
            'midp',
            'wap',
            'mobile',
        ];
        /* 从HTTP_USER_AGENT中查找手机浏览器的关键字 */
        if (preg_match('/(' . implode('|', $clientkeywords) . ')/i', strtolower($_SERVER['HTTP_USER_AGENT']))) {
            return true;
        }
    }

    /* 协议法，因为有可能不准确，放到最后判断 */
    if (isset($_SERVER['HTTP_ACCEPT'])) {
        /* 如果只支持wml并且不支持html那一定是移动设备 */
        /* 如果支持wml和html但是wml在html之前则是移动设备 */
        if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) {
            return true;
        }
    }
    return false;
}

/**
 * 清空目录下所有文件.
 * @return bool
 */
function EmptyDir($dir_path): bool
{
    if (is_dir($dir_path)) {
        $dn = @opendir($dir_path);
        if ($dn !== false) {
            while (false !== ($file = readdir($dn))) {
                if ($file != '.' && $file != '..') {
                    if (! unlink($dir_path . $file)) {
                        return false;
                    }
                }
            }
        } else {
            return false;
        }
    }
    return true;
}

/**
 * 文件大小转常用单位.
 */
function FileSizeByteToUnit($bit): string
{
    // 单位每增大1024，则单位数组向后移动一位表示相应的单位
    $type = [
        'Bytes',
        'KB',
        'MB',
        'GB',
        'TB',
    ];
    for ($i = 0; $bit >= 1024; ++$i) {
        $bit /= 1024;
    }

    // floor是取整函数，为了防止出现一串的小数，这里取了两位小数
    return (floor($bit * 100) / 100) . $type[$i];
}

/**
 * json带格式输出.
 */
function JsonFormat($data, $indent = null): string
{
    // json encode
    $data = json_encode($data, JSON_UNESCAPED_UNICODE);

    // 缩进处理
    $ret = '';
    $pos = 0;
    $length = strlen($data);
    $indent = isset($indent) ? $indent : '    ';
    $newline = "\n";
    $prevchar = '';
    $outofquotes = true;

    for ($i = 0; $i <= $length; ++$i) {
        $char = substr($data, $i, 1);

        if ($char == '"' && $prevchar != '\\') {
            $outofquotes = ! $outofquotes;
        } elseif (($char == '}' || $char == ']') && $outofquotes) {
            $ret .= $newline;
            --$pos;
            for ($j = 0; $j < $pos; ++$j) {
                $ret .= $indent;
            }
        }

        $ret .= $char;

        if (($char == ',' || $char == '{' || $char == '[') && $outofquotes) {
            $ret .= $newline;
            if ($char == '{' || $char == '[') {
                ++$pos;
            }

            for ($j = 0; $j < $pos; ++$j) {
                $ret .= $indent;
            }
        }

        $prevchar = $char;
    }

    return $ret;
}

/**
 * 根据身份证号码得到年龄.
 *
 * @return float|int|string
 */
function getAgeByID(string $id)
{ // 过了这年的生日才算多了1周岁
    if (empty($id)) {
        return '';
    }
    $date = strtotime(substr($id, 6, 8)); // 获得出生年月日的时间戳
    $today = strtotime('today'); // 获得今日的时间戳
    $diff = floor(($today - $date) / 86400 / 365); // 得到两个日期相差的大体年数
    // strtotime加上这个年数后得到那日的时间戳后与今日的时间戳相比
    return strtotime(substr($id, 6, 8) . '+' . $diff . 'years') > $today ? ($diff + 1) : $diff;
}

/**
 * API请求地址
 */
function web_url(): string
{
    return http_type() . $_SERVER['HTTP_HOST'];
}

/**
 * 获取http类型：http\https.
 */
function http_type(): string
{
    return $http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';
}

/**
 * 格式化字节大小.
 * @param number $size 字节数
 * @param string $delimiter 数字和单位分隔符
 * @return string 格式化后的带单位的大小
 */
function format_bytes($size, string $delimiter = ''): string
{
    $units = [
        'B',
        'KB',
        'MB',
        'GB',
        'TB',
        'PB',
    ];
    for ($i = 0; $size >= 1024 && $i < 5; ++$i) {
        $size /= 1024;
    }
    return $size . $delimiter . $units[$i];
}

// 随机验证码
if (! function_exists('random_verification_code')) {
    function random_verification_code($length = 6): string
    {
        $code = '';
        for ($i = 0; $i < $length; ++$i) {
            $code .= mt_rand(0, 9);
        }
        return $code;
    }
}

if (! function_exists('get_dir_files')) {
    // 获取某目录下所有子文件和子目录,可以过滤
    function get_dir_files($path, $filter = [], $onlydir = false): array|bool
    {
        if (! is_dir($path)) {
            return false;
        }
        // scandir方法
        $arr = [];
        $data = scandir($path);
        foreach ($data as $value) {
            if ($value != '.' && $value != '..' && $value != '.DS_Store' && ! in_array($value, $filter)) {
                if ($onlydir) {
                    if (is_dir($path . '/' . $value)) {
                        $arr[] = $value;
                    } else {
                        continue;
                    }
                } else {
                    $arr[] = $value;
                }
            }
        }
        return $arr;
    }

    // 列出指定目录下所有目录和文件
    function get_dir_files1($dir): array
    {
        $arr = [];
        if (is_dir($dir)) {// 如果是目录，则进行下一步操作
            $d = opendir($dir); // 打开目录
            if ($d) {// 目录打开正常
                while (($file = readdir($d)) !== false) {// 循环读出目录下的文件，直到读不到为止
                    if ($file != '.' && $file != '..') {// 排除一个点和两个点
                        if (is_dir($file)) {// 如果当前是目录
                            $arr[$file] = get_dir_files($file); // 进一步获取该目录里的文件
                        } else {
                            $arr[] = $file; // 记录文件名
                        }
                    }
                }
            }
            closedir($d); // 关闭句柄
        }
        return $arr;
    }
}

if (! function_exists('write_lock_file')) {
    /**
     * 写入锁文件.
     */
    function write_lock_file(string $path, string $content = ''): void
    {
        $lock_file = fopen($path . '/lock', 'w+'); // 创建 锁文件
        fwrite($lock_file, empty($content) ? date('Y-m-d H:i:s') : $content); // 写入
    }
}

if (! function_exists('put_file_to_zip')) {
    /**
     * 把指定文件目录下的所有文件，打包压缩至压缩包内.
     *
     * @param array $limit_dir 限制压缩的文件目录
     * @param mixed $zip
     */
    function put_file_to_zip(string $path, $zip, string $old_filename, array $limit_dir = []): void
    {
        header('content-type:text/html;charset=utf-8');
        $handler = opendir($path); // 打开当前文件夹由$path指定。
        while (($filename = readdir($handler)) !== false) {
            if ($filename != '.' && $filename != '..') {// 文件夹文件名字为'.'和‘..’，不要对他们进行操作
                if (is_dir($path . '/' . $filename)) {// 如果读取的某个对象是文件夹，则递归
                    if (! empty($limit_dir) && ! in_array($filename, $limit_dir)) {
                        continue;
                    }
                    $old_filename = (empty($old_filename) ? '' : ($old_filename . '/'));
                    $zip->addEmptyDir($old_filename . $filename);
                    put_file_to_zip($path . '/' . $filename, $zip, $old_filename . $filename);
                } else { // 将文件加入zip对象
                    $zip->addFile($path . '/' . $filename, (empty($old_filename) ? '' : ($old_filename . '/')) . $filename);
                }
            }
        }
        @closedir($path);
    }
}

if (! function_exists('check_http_file_exists')) {
    // 判断远程文件是否存在
    function check_http_file_exists(string $url): bool
    {
        $curl = curl_init($url);
        // 不取回数据
        curl_setopt($curl, CURLOPT_NOBODY, true);
        // 发送请求
        $result = curl_exec($curl);
        $found = false;
        // 如果请求没有发送失败
        if ($result !== false) {
            $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
            if ($statusCode == 200) {
                $found = true;
            }
        }
        curl_close($curl);
        return $found;
    }
}

if (! function_exists('check_dir_exits')) {
    /**
     * 检测目录是否存在，不存在则创建目录.
     */
    function check_dir_exits(string $dir_path): void
    {
        if (! is_dir($dir_path)) {
            @mkdir($dir_path, 0755, true);
        }
    }
}


/**
 * 把时间戳转换为几分钟或几小时前或几天前
 * @param $time
 * @return string
 */
function formatting_timestamp($time): string
{
    $time = (int) substr($time, 0, 10);
    $int = time() - $time;
    $str = '';
    if ($int <= 30) {
        $str = sprintf('刚刚', $int);
    } elseif ($int < 60) {
        $str = sprintf('%d秒前', $int);
    } elseif ($int < 3600) {
        $str = sprintf('%d分钟前', floor($int / 60));
    } elseif ($int < 86400) {
        $str = sprintf('%d小时前', floor($int / 3600));
    } elseif ($int < 2592000) {
        $str = sprintf('%d天前', floor($int / 86400));
    } elseif (date('Y', $time) == date('Y')) {
        $str = date('m-d H:i:s', $time);
    } else {
        $str = date('Y-m-d H:i:s', $time);
    }
    return $str;
}

// 统计字数
function comment_count_word($str): int
{
    // $str =characet($str);
    // 判断是否存在替换字符
    $is_tihuan_count = substr_count($str, '龘');
    try {
        // 先将回车换行符做特殊处理
        $str = preg_replace('/(\r\n+|\s+|　+)/', '龘', $str);
        // 处理英文字符数字，连续字母、数字、英文符号视为一个单词
        $str = preg_replace('/[a-z_A-Z0-9-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",\'<>~`\?:;|]/', 'm', $str);
        // 合并字符m，连续字母、数字、英文符号视为一个单词
        $str = preg_replace('/m+/', '*', $str);
        // 去掉回车换行符
        $str = preg_replace('/龘+/', '', $str);
        // 返回字数
        return mb_strlen($str) + $is_tihuan_count;
    } catch (Exception $e) {
        return 0;
    }
}

function exist_http($str): bool
{
    return preg_match('/(http:\/\/)|(https:\/\/)/i', $str);
}

/**
 * 生成订单号.
 */
function order_no(): string
{
    return date('Ymd') . substr(implode('', array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
/**
 * 获取request数据初始化.
 * @return mixed|RequestInterface
 */
function get_request(): mixed
{
    return Context::get(RequestInterface::class);
}

/**
 * 获取session数据初始化.
 * @return mixed|SessionInterface
 */
function get_session(): mixed
{
    return Context::get(SessionInterface::class);
}
/**
 * 刷新当前管理员session.
 * @return null|void
 */
function refresh_admin_session(bool $force = false)
{
    $session = get_session();
    $admin_session = $session->get('admin');
    if (! $admin_session) {
        return null;
    }
    $admin_id = $admin_session['id'];
    $time_now = time();
    // session在2秒内不刷新
    $session_ttl = 2;
    $session_last_update_time = $session->get('admin.session_last_update_time', 0);
    if (! $force && $time_now - $session_last_update_time < $session_ttl) {
        return null;
    }

    $admin = Admin::query()->find($admin_id);
    if (! $admin) {
        $session->forget('admin');
        return null;
    }
    $admin = $admin->toArray();
    $admin['password'] = md5($admin['password']);
    $admin_session['password'] = $admin_session['password'] ?? '';
    if ($admin['password'] != $admin_session['password']) {
        $session->forget('admin');
        return null;
    }
    // 账户被禁用
    if ($admin['status'] != 0) {
        $session->forget('admin');
        return;
    }
    $admin['roles'] = AdminRole::query()->where('admin_id', $admin_id)->pluck('role_id')->toArray();
    $admin['session_last_update_time'] = $time_now;
    $session->set('admin', $admin);
}

/**
 * 当前管理员id.
 */
function admin_id(): ?int
{
    $session = get_session();
    return $session->get('admin.id');
}

/**
 * 当前管理员.
 */
function admin($fields = null): ?array
{
    $session = get_session();
    refresh_admin_session();
    if (! $admin = $session->get('admin')) {
        return null;
    }
    if ($fields === null) {
        return $admin;
    }
    if (is_array($fields)) {
        $results = [];
        foreach ($fields as $field) {
            $results[$field] = $admin[$field] ?? null;
        }
        return $results;
    }
    return $admin[$fields] ?? null;
}

/**
 * 获取请求的 URL
 * @param bool $bool 是否查询参数,true是带上查询参数
 * @return string
 */
function get_url(bool $bool=false): string
{
    $request = get_request();
    if ($bool){
        return $request->fullUrl();
    }
    return $request->url();
}


/**
 * 协程请求数据
 * @param string $url
 * @param array $data
 * @param string $mate_type
 * @param bool $bool
 * @return string|array
 */
function http_pool_curl(string $url,array $data=[],string $mate_type="get",bool $bool=false): string|array
{
    //第一种方式[只能在控制器中进行注解可以实现]
    //$clientFactory= Context::get(ClientFactory::class);
    // $options 等同于 GuzzleHttp\Client 构造函数的 $config 参数
    //$options = [];
    // $client 为协程化的 GuzzleHttp\Client 对象
    //$client = $clientFactory->create($options);
    //第二种方式
    //$client = guzzle_stack();
    //第三种方式
    //$client = new Client();
    //第四种方式
    $client=guzzle_handler();
    try {
        if ($mate_type=="post"){
            if ($bool){
                $promise = $client->postAsync($url,$data);
            }else{
                $promise = $client->post($url,$data);
            }
        }else{
            if ($bool){
                $promise = $client->getAsync($url);
            }else{
                $promise = $client->get($url);
            }
        }
        if ($bool){
            $response = $promise->wait();
        }else{
            $response = $promise;
        }
        $data = $response->getBody()->getContents();
        if (is_json($data)){
            return json_decode($data,true);
        }
        return $data;
    } catch (\GuzzleHttp\Exception\GuzzleException $e) {
        return $e->getMessage();
    }
}

/**
 * 链接池方式初始化Guzzle Http客户端
 * @return Client
 */
function guzzle_handler(): Client
{
    $handler = null;
    if (Coroutine::inCoroutine()) {
        $handler = make(PoolHandler::class, [
            'option' => [
                'max_connections' => 50,
            ],
        ]);
    }
    // 默认的重试Middleware
    $retry = make(RetryMiddleware::class, [
        'retries' => 1,
        'delay' => 10,
    ]);
    $stack = HandlerStack::create($handler);
    $stack->push($retry->getMiddleware(), 'retry');
    return make(Client::class, [
        'config' => [
            'handler' => $stack,
        ],
    ]);
}

/**
 * HandlerStackFactory方式初始化Guzzle Http客户端
 * @return Client
 */
function guzzle_stack():Client
{
    $factory = new HandlerStackFactory();
    $stack = $factory->create();
    return make(Client::class, [
        'config' => [
            'handler' => $stack,
        ],
    ]);
}


/**
 * 当前毫秒时间戳
 * @return float
 */
function getMillisecondTimestamp(): float
{
    list($microSeconds, $seconds) = explode(" ", microtime());
    $milliseconds = round(((float)$microSeconds * 1000));
    return (float)sprintf("%.0f", (float)$seconds + $milliseconds);
}

/**
 * 生成CSRFToken
 * @return int|string
 * @throws ContainerExceptionInterface
 * @throws NotFoundExceptionInterface
 */
function generateCSRFToken(): int|string
{
    // 使用random_bytes生成一个32字节的随机字符串
    try {
        $token = bin2hex(random_bytes(32));
    } catch (\Random\RandomException $e) {
        $token = get_snowflake_id();
    }
    return $token;
}

/**
 * 返回特定字符前的字符串
 * @param string $connect
 * @param string $needle_sting
 * @return string
 */
function get_needle_sting(string $connect,string $needle_sting): string
{
    return substr($connect,0,strrpos($connect ,$needle_sting));
}